/*
 * "TaringaConnection.java" is part of Tarinca-COA
 * (http://code.google.com/p/taringa-api/)
 * Copyright (C) - 2010 Juan B Cabral <jbc dot develop at gmail dot com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */
package taringa;

import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import taringa.json.simple.JSONArray;
import taringa.json.simple.JSONObject;
import taringa.json.simple.parser.JSONParser;

/**
 * Conexión con Taringa
 *
 * @version 0.2
 * @license LGPL3
 * @author JBC
 * @since 0.2
 * @date 2010-02-13
 */
public class TaringaConnection {

    private String url;
    private String key;
    private JSONParser jsonParser;

    /**
     * Crea una nueva conexion a taringa.
     *
     * @param url la url para conectarse
     * @param key la llave unica del desarrollador
     *
     */
    public TaringaConnection(String url, String key) {
        if (url.endsWith("/")) {
            this.url = url.substring(0, url.length() - 1);
        } else {
            this.url = url;
        }
        this.key = key.trim();
        this.jsonParser = new JSONParser();
    }

    private Map<String, Object> sendRequest(String call, Object... args) {
        String fullUrl = this.url + "/api/" + this.key + "/json/" + call;
        for (Object arg : args) {
            if (arg == null) {
                arg = "";
            }
            fullUrl += "/" + arg;
        }
        Map<String, Object> response = new JSONObject();
        try {
            String jsonSrc = UrlLib.urlOpen(fullUrl);
            if (!jsonSrc.isEmpty()) {
                Object objResponse = this.jsonParser.parse(jsonSrc);
                if (!(objResponse instanceof JSONArray)) {
                    response = (Map<String, Object>) objResponse;
                }
            }
        } catch (Exception ex) {
            throw new TaringaError(ex.getMessage(), 1000, ex.getStackTrace());
        }
        if (response.containsKey("error")) {
            Map<String, String> e = (Map<String, String>) response.get("error");
            String msg = e.get("msg");
            long code = Long.parseLong(e.get("code"));
            throw new TaringaError(msg, code);
        }
        return response;
    }

    private Map<String, Object> sendRequest(Call c, Object... args) {
        String call = c.getValue();
        return this.sendRequest(call, args);
    }

    @Override
    public String toString() {
        return "<Taringa: " + this.url + "{" + this.key + "}>";
    }

    /**
     * Retorna la llave de acceso al api.
     *
     * @return la llave de acceso al api.
     *
     */
    public String getKey() {
        return key;
    }

    /**
     * Cambia la llave de acceso al api.
     *
     * @param la llave de acceso al api.
     *
     */
    public void setKey(String key) {
        this.key = key;
    }

    /**
     * La url donde se encuentra la instancia de taringa.
     *
     * @return La url donde se encuentra la instancia de taringa.
     *
     */
    public String getUrl() {
        return url;
    }

    /**
     * Altera la url donde se encuentra la instancia de taringa.
     *
     * @param url la url donde se encuentra la instancia de taringa.
     *
     */
    public void setUrl(String url) {
        this.url = url;
    }

    /**
     * Se lista la información sobre el usuario indicado.
     *
     * @throws TaringaErrorHasta 20 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @param nick correspondiente del usuario a consultar.
     *
     * @return Map <br/>
     *    return.get("userlevel"): rango del usuario.<br/>
     *    return.get("sex"): sexo del usuario.<br/>
     *    return.get("city"): ciudad de residencia del usuario.<br/>
     *    return.get("country"): país del residencia del usuario.<br/>
     *    return.get("avatar"): URL del avatar del usuario.<br/>
     *    return.get("website"): sitio web del usuario.<br/>
     *    return.get("personal-message"): mensaje personal del usuario.<br/>
     *    return.get("creation-date"): fecha de registro de la cuenta.<br/>
     *    return.get("score"): puntos totales del usuario.<br/>
     *    return.get("total-posts"): posts totales creados por el usuario.<br/>
     *    return.get("total-comments"): comentarios totales hechos por el usuario.<br/>
     *    return.get("trank"): puesto en el T! Rank del usuario.<br/>
     *    * return.get("name"): nombre del usuario.<br/>
     *    * return.get("e-mail"): E-mail del usuario.<br/>
     *    * return.get("birthdate"): fecha de nacimiento del usuario.<br/>
     *    * return.get("years"): edad del usuario.<br/>
     *    * return.get("im"): mensajero del usuario.<br/>
     *
     * Los campos indicados con '*' pueden estar en null dadas las configuraciones de privacidad del usuario
     *
     */
    public Map<String, Object> usersGetUserData(String nick) {
        Map<String, Object> r = this.sendRequest(Call.CALL_USERS_GET_USER_DATA, nick);
        Map<String, Object> profile = (Map<String, Object>) r.get("profile");
        profile.put("score", TypeParser.toLong(profile.get("score")));
        profile.put("creation-date", TypeParser.toDate(profile.get("creation-date")));
        profile.put("total-posts", TypeParser.toLong(profile.get("total-posts")));
        profile.put("total-comments", TypeParser.toLong(profile.get("total-comments")));
        if (!profile.containsKey("name")) {
            profile.put("name", null);
        }
        if (!profile.containsKey("e-mail")) {
            profile.put("email", null);
        }
        if (!profile.containsKey("im")) {
            profile.put("im", null);
        }
        if (!profile.containsKey("years")) {
            profile.put("years", null);
        } else {
            profile.put("years", TypeParser.toLong(profile.get("years")));
        }
        if (!profile.containsKey("birthdate")) {
            profile.put("birthdate", null);
        } else {
            profile.put("birthdate", TypeParser.toDate(profile.get("birthdate") + " 00:00:00"));
        }
        return profile;
    }

    /**
     * Se listan todos los rangos de los usuarios.
     *
     * @throws TaringaErrorHasta 5 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @return Map[]
     *     return[i].get("name"): nombre del rango.
     *     return[i].get("image"): URL del ícono representativo del rango.
     *
     */
    public Map<String, Object>[] usersGetLevels() {
        Collection lvls = this.sendRequest(Call.CALL_USERS_GET_LEVELS).values();
        return (Map<String, Object>[]) lvls.toArray(new Map[lvls.size()]);
    }

    /**
     * Muestra los ultimos 25 comentarios en posts del usuario.
     *
     * @throws TaringaError Hasta 20 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @param nick correspondiente del usuario a consultar.
     *
     * @return map[]
     *     return[i].get("comment-id"): id del comentario
     *     return[i].get("date"): fecha que fue realizado el comentario.
     *     return[i].get("post-id"): post donde se realizo el comentario.
     *     return[i].get("comment"): texto del comentario.
     *     return[i].get("url"): url donde se encuentra el comentario
     *
     */
    public Map<String, Object>[] usersGetPostsComments(String nick) {
        List<Map> comments = new Vector<Map>();
        Map<String, Object> r = this.sendRequest(Call.CALL_USERS_GET_POSTS_COMMENTS, nick);
        for (Object obj : r.values()) {
            Map<String, Object> cmmt = (Map<String, Object>) obj;
            cmmt.put("post-id", TypeParser.toLong(cmmt.get("post-id")));
            cmmt.put("date", TypeParser.toDate(cmmt.get("date")));
            cmmt.put("comment-id", TypeParser.toLong(cmmt.get("comment-id")));
            comments.add(cmmt);
        }
        return comments.toArray(new Map[comments.size()]);
    }

    /**
     * Muestra los ultimos 25 posts creados por el usuario.
     *
     * @throws TaringaError Hasta 30 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @param nick correspondiente del usuario a consultar.
     *
     * @return Map[]
     *     return[i].get("post-id"): ID del post correspondiente.
     *     return[i].get("cat-id"): ID de la categoria del post.
     *     return[i].get("title"): titulo del post.
     *     return[i].get("creation-date"): fecha de creacion del post.
     *     return[i].get("no-comments"): indica si el post tiene los comentarios cerrados (True/False).
     *     return[i].get("private"): indica si el post es solo para usuarios registrados (True/False).
     *     return[i].get("score"): puntos que recibio del post.
     *     return[i].get("url"): URL del post.
     *
     */
    public Map<String, Object>[] usersGetPostsList(String nick) {
        List<Map> posts = new Vector<Map>();
        Map<String, Object> r = this.sendRequest(Call.CALL_USERS_GET_POSTS_LIST, nick);
        for (Object objValue : r.values()) {
            Map post = new HashMap((Map) objValue);
            post.put("no-comments", TypeParser.toBoolean(post.get("no-comments")));
            post.put("post-id", TypeParser.toLong(post.get("post-id")));
            post.put("cat-id", TypeParser.toLong(post.get("cat-id")));
            post.put("score", TypeParser.toLong(post.get("score")));
            post.put("creation-date", TypeParser.toDate(post.get("creation-date")));
            post.put("private", TypeParser.toBoolean(post.get("private")));
            posts.add(post);
        }
        return posts.toArray(new Map[posts.size()]);
    }

    /**
     * Se listan todas las categorías del sitio.
     *
     * @throws TaringaError Hasta 5 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @return Map[]
     *    return[i].get("id"): ID correspondiente a la categoría.
     *    return[i].get("idname"): nombre de la categoría en formato URL.
     *    return[i].get("name"): nombre de la categoría.
     *
     */
    public Map<String, Object>[] postsGetCategories() {
        Vector<Map> categories = new Vector<Map>();
        Map<String, Object> r = this.sendRequest(Call.CALL_POSTS_GET_CATEGORIES);
        for (Object objValue : r.values()) {
            Map cat = new HashMap((Map) objValue);
            categories.add(cat);
        }
        return categories.toArray(new Map[categories.size()]);
    }

    /**
     * Se listan los 100 tags más utilizados en todos los posts del sitio.
     *
     * @throws TaringaError Hasta 5 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @return Map[]
     *     return[i].get("position"): posición en el ranking de tags más populares (1 para el más popular).
     *     return[i].get("tag"): tag en cuestión.
     *     return[i].get("total"): total de posts que utiliza.
     *
     */
    public Map<String, Object>[] postsGetCloudTags() {
        Vector<Map> tags = new Vector<Map>();
        Map<String, Object> r = this.sendRequest(Call.CALL_POSTS_GET_CLOUD_TAGS);
        for (Object objValue : r.values()) {
            Map<String, Object> tag = new HashMap((Map) objValue);
            long total = TypeParser.toLong(tag.get("total"));
            tag.put("total", total);
            tags.add(tag);
        }
        return tags.toArray(new Map[tags.size()]);
    }

    /**
     * Muestra las estadísticas e información del sitio.
     *
     * @throws TaringaError Hasta 5 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @return Map
     *    return.get("users-online"): usuarios online.
     *    return.get("members"): usuarios registrados.
     *    return.get("posts"): posts totales.
     *    return.get("comments"): comentarios totales.
     *
     */
    public Map<String, Object> siteGetSiteStats() {
        Map<String, Object> stats = this.sendRequest(Call.CALL_SITE_GET_SITE_STATS);
        for (Map.Entry<String, Object> entry : stats.entrySet()) {
            Object value = entry.getValue();
            entry.setValue(TypeParser.toLong(value));
        }
        return stats;
    }

    /**
     * Se listan los 10 top posts según los criterios seleccionados.
     *
     * @throws TaringaError Hasta 10 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @param time:
     *     TimeFilter.ALL_TIME: Todos los tiempos
     *     TimeFilter.TODAY: Hoy
     *     TimeFilter.YESTERDAY: Ayer
     *     TimeFilter.LAST_WEEK: últimos 7 días
     *     TimeFilter.LAST_MONTH: últimos 30 días
     *     TimeFilter.LAS_TWO_MONTHS: últimos 60 días
     *
     * @param post_top:
     *     PostTop.MAX_SCORE: Posts con más puntos
     *     PostTop.MAX_COMMENTS: Posts más comentados
     *     PostTop.MOST_FAVORITES: Posts más guardados en favoritos
     *     PostTop.MOST_VISITED: Posts más visitados
     *
     * @param post_category:
     *     ID de la categoría correspondiente)
     *     -1: Todas las categorías
     *
     * @return Map[]
     *     return[i].get("position"): puesto del ranking.
     *     return[i].get("location"): URL del post.
     *     return[i].get("title"): título del post.
     *     return[i].get("cat"): categoría del post.
     *     return[i].get("date"): fecha de creación del post.
     *     return[i].get("score"): valor representativo del top (posts/puntos/comentarios/favoritos)
     *     return[i].get("unit"): unidad del top (comments/posts/points/favorites)
     *
     */
    public Map<String, Object>[] postsGetTopPosts(TimeFilter time, PostTop type, long category) {
        Vector<Map> posts = new Vector<Map>();
        Map<String, Object> r = this.sendRequest(Call.CALL_POSTS_GET_TOP_POSTS, time, type, category);
        for (Object obj : r.values()) {
            Map<String, Object> post = (Map<String, Object>) obj;
            post.put("position", TypeParser.toLong(post.get("position")));
            post.put("cat", TypeParser.toLong(post.get("cat")));
            post.put("date", TypeParser.toDate(post.get("date")));
            post.put("score", TypeParser.toLong(post.get("score")));
            posts.add(post);
        }
        return posts.toArray(new Map[posts.size()]);
    }

    /**
     * Se listan los 15 usuarios top según los criterios seleccionados.
     *
     * @throws TaringaError Hasta 10 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @param time:
     *     TIME_ALL_TIME (-1): Todos los tiempos
     *     TIME_TODAY: Hoy
     *     TIME_YESTERDAY: Ayer
     *     TIME_LAST_WEEK: Últimos 7 días
     *     TIME_LAST_MONTH: Últimos 30 días
     *     TIME_LAS_TWO_MONTHS: Últimos 60 días
     *
     * @param post_category:
     *     ID de la categoría correspondiente)
     *     -1: Todas las categorías
     *
     * @param user_top:
     *     UserTop.MAX_POST: Usuario con más posts
     *     UserTop.MAX_SCORE: Usuario con más puntos
     *
     * @return Map[]
     *     return[i].get("position"): puesto del ranking.
     *     return[i].get("nick"): nick del usuario.
     *     return[i].get("score"): valor representativo del top (posts/puntos)
     *     return[i].get("unit"): unidad del top (posts/points)
     *
     *
     */
    public Map<String, Object>[] postsGetTopUsers(TimeFilter time, UserTop type, int category) {
        Vector<Map> users = new Vector<Map>();
        Map<String, Object> r = this.sendRequest(Call.CALL_POSTS_GET_TOP_POSTS, time, type, category);
        for (Object obj : r.values()) {
            Map<String, Object> usr = (Map<String, Object>) obj;
            usr.put("position", TypeParser.toLong(usr.get("position")));
            usr.put("author-id", TypeParser.toLong(usr.get("author-id")));
            usr.put("score", TypeParser.toLong(usr.get("score")));
            users.add(usr);
        }
        return users.toArray(new Map[users.size()]);
    }

    /**
     * Obtiene la tupla de los últimos 50 posts de la categoría seleccionada.
     *
     * @throws TaringaError Hasta 10 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @param category: ID de la categoría de la cual se desean obtener los últimos posts.
     *    11: corresponde a los posts de Novatos - múltiples categorías
     *    -1: corresponde a todos los posts de Full Users - mutiples categorías
     *
     * @param page: número de página que se desea visualizar (1= primeros 50 posts, 2=del 51 al 100, etc.).
     *
     * @return Map[]
     *    return[i].get("id"): ID del post.
     *    return[i].get("title"): título del post.
     *    return[i].get("category"): ID de la categoría del post.
     *    return[i].get("private"): indica si el post es privado  (true = privado, false = público).
     *    return[i].get("url"): URL del post.
     *
     */
    public Map<String, Object>[] postsGetPostList(long category, long page) {
        Vector<Map> posts = new Vector<Map>();
        Map<String, Object> r = this.sendRequest(Call.CALL_POSTS_GET_POST_LIST, category, page);
        for (Object obj : r.values()) {
            Map<String, Object> post = (Map<String, Object>) obj;
            post.put("id", TypeParser.toLong(post.get("id")));
            post.put("category", TypeParser.toLong(post.get("category")));
            post.put("private", TypeParser.toBoolean(post.get("private")));
            posts.add(post);
        }
        return posts.toArray(new Map[posts.size()]);
    }

    /**
     * Se lista la información sobre el post indicado.
     *
     * @throws TaringaError Hasta 30 llamadas por minuto o se lanza un TaringaError con
     *                   código 11 (Rate limiting exceded).
     *
     * @param id: ID correspondiente del post a consultar.
     *
     * @return Map
     *    return.get("title"): título del post.
     *    return.get("cat"): ID correspondiente a la categoría del post.
     *    return.get("date"): fecha de creación del post.
     *    return.get("url"): URL del post.
     *    return.get("autor"): nick del autor del post.
     *    return.get("score"): puntos totales del post.
     *    return.get("favorites"): total de usuarios que lo guardaron en sus favoritos.
     *    return.get("visits"): visitas totales del post.
     *    return.get("related"): lista de posts relacionados.
     *            return.get("related")[i].get("id"): ID del post relacionado.
     *            return.get("related")[i].get("title"): título del post relacionado.
     *            return.get("related")[i].get("cat"): ID correspondiente a la categoría  del post relacionado.
     *            return.get("related")[i].get("url"): URL del post relacionado.
     *    return.get("tags"): lista de tags relacionados.
     *    return.get("private"): valor para indicar si el post es privado (false = público | true = privado).
     *    return.get("allow-comments"): indica si el post acepta comentarios (false = acepta | true = no acepta).
     *    return.get("newbie"): indica si el post corresponde a la categoría Novatos (false = no es Novato | true = es Novato).
     *    return.get("sticky"): indica si el post es un sticky (false = no es sticky | true = es sticky).
     *
     */
    public Map<String, Object> postsGetPostData(long id) {
        Map<String, Object> post = this.sendRequest(Call.CALL_POSTS_GET_POST_DATA, id);
        String[] tags = ((String) post.get("tags")).split(", ");
        post.put("tags", tags);
        post.put("cat", TypeParser.toLong(post.get("cat")));
        post.put("score", TypeParser.toLong(post.get("score")));
        post.put("favorites", TypeParser.toLong(post.get("favorites")));
        post.put("date", TypeParser.toDate(post.get("date")));
        post.put("private", TypeParser.toBoolean(post.get("private")));
        post.put("newbie", TypeParser.toBoolean(post.get("newbie")));
        post.put("sticky", TypeParser.toBoolean(post.get("sticky")));
        post.put("visits", TypeParser.toLong(post.get("visits")));
        post.put("allow-comments", TypeParser.toBoolean(post.get("allow-comments")));
        Vector<Map> related = new Vector<Map>();
        if (post.containsKey("related")) {
            for (Object obj : ((Map) post.get("related")).values()) {
                Map<String, Object> r = (Map<String, Object>) obj;
                r.put("id", TypeParser.toLong(r.get("id")));
                r.put("cat", TypeParser.toLong(r.get("cat")));
                related.add(r);
            }
        }
        post.put("related", related.toArray(new Map[related.size()]));
        return post;
    }

    /**
     * Muestra los comentarios del post deseado.
     *
     * @throws TaringaError Hasta 30 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @param id: ID correspondiente del post a consultar.
     *
     * @return Map[]
     *    return[i].get("comment-id"): ID del comentario.
     *    return[i].get("nick"): nick del autor.
     *    return[i].get("date"): fecha de creación del post.
     *    return[i].get("comment"): texto del comentario.
     *
     */
    public Map<String, Object>[] postsGetPostComments(long id) {
        Vector<Map> comments = new Vector<Map>();
        Map<String, Object> r =
                this.sendRequest(Call.CALL_POSTS_GET_POST_COMMENTS, id);
        for (Object obj : r.values()) {
            Map<String, Object> cmmt = (Map<String, Object>) obj;
            cmmt.put("date", TypeParser.toDate(cmmt.get("date")));
            cmmt.put("comment-id", TypeParser.toLong(cmmt.get("comment-id")));
            comments.add(cmmt);
        }
        return comments.toArray(new Map[comments.size()]);
    }

    /**
     * Se realiza una búsqueda en todos los posts con el término elegido.
     *
     * Se pueden filtrar por categorías, autor y ordenar por puntos o fecha de creación.
     *
     * @throws TaringaError Hasta 30 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @param query: término que se desea buscar.
     *
     *  @param category:
     *    ID de la categoría correspondiente)
     *    -1: Todas las categorías
     *
     * @param orden: orden con el cual se desean organizar los posts.
     *       OrderQuery.RELEVANCE: relevancia
     *       OrderQuery.DATE: fecha
     *       OrderQuery.SCORE: puntos
     *
     * @param autor: nick del autor por el cual se desea filtrar la búsqueda.
     *
     * @param página: número de página de resultados que se desean obtener (50 resultados por página).
     *
     * @return Map[]
     *     return[i].get("id"): ID del post.
     *     return[i].get("title"): título del post.
     *     return[i].get("autor"): nick del autor del post.
     *     return[i].get("score"): puntos del posts.
     *     return[i].get("cat"): ID de la categoría del post.
     *     return[i].get("private"): indica si el post es cerrado solo a usuarios. (false = abierto, true = cerrado)
     *     return[i].get("date"): fecha de creación del post.
     *     return[i].get("url"): URL del post.
     *
     *
     */
    public Map<String, Object>[] postsGetSearch(String query, long category, OrderQuery order, String author, long page) {
        Vector<Map> posts = new Vector<Map>();
        Map<String, Object> r = this.sendRequest(Call.CALL_POSTS_GET_SEARCH, query, category, order, author, page);
        for (Object obj : r.values()) {
            Map<String, Object> post = (Map<String, Object>) obj;
            post.put("id", TypeParser.toLong(post.get("id")));
            post.put("cat", TypeParser.toLong(post.get("cat")));
            post.put("score", TypeParser.toLong(post.get("score")));
            post.put("date", TypeParser.toDate(post.get("date")));
            post.put("private", TypeParser.toBoolean(post.get("private")));
            posts.add(post);
        }
        return posts.toArray(new Map[posts.size()]);
    }

    /**
     * Muestra los posts relacionados con el tag elegido. Se pueden filtrar por categorías y ordenar por puntos o fecha de creación.
     *
     * @throws TaringaError Hasta 30 llamadas por minuto o se lanza un TaringaError con código 11 (Rate limiting exceded).
     *
     * @param tag: tag elegido para la consulta.
     *
     * @param category:
     *     ID de la categoría correspondiente
     *     -1: Todas las categorías
     *
     * @param orden: orden con el cual se desean organizar los posts.
     *     OrderTag.SCORE: puntos
     *     OrderTag.DATE: fecha
     *
     * @param página: número de página de resultados que se desean obtener (50 resultados por página).
     *
     * @return Map[]
     *    return[i].get("id"): ID del post.
     *    return[i].get("title"): título del post.
     *    return[i].get("cat"): ID de la categoría del post.
     *    return[i].get("date"): fecha de creación del post.
     *    return[i].get("score"): puntos del posts.
     *    return[i].get("url"): URL del post.
     *
     */
    public Map<String, Object>[] postsGetPostsTag(String tag, int category, OrderTag order, int page) {
        Vector<Map> posts = new Vector<Map>();
        Map<String, Object> r = this.sendRequest(Call.CALL_POSTS_GET_POSTS_TAG, tag, category, order, page);
        System.out.println(r.values().toArray()[0]);
        for (Object obj : r.values()) {
            Map<String, Object> post = (Map<String, Object>) obj;
            post.put("id", TypeParser.toLong(post.get("id")));
            post.put("cat", TypeParser.toLong(post.get("cat")));
            post.put("score", TypeParser.toLong(post.get("score")));
            post.put("date", TypeParser.toDate(post.get("date")));
            posts.add(post);
        }
        return posts.toArray(new Map[posts.size()]);
    }

    /**
     * Indica la cantidad de consultas disponibles para otra llamada.
     *
     * En el caso de hacer la consulta antes de inciar los procesos de llamadas
     * se obtiene el total de consultas disponibles para cada funcion, de esta
     * se evita la necesidad de preasignar estos valores.
     *
     * @param call: El nombre de la llamada a consultar.
     *
     * @return long
     *
     */
    public long getRate(Call call) {
        String callValue = call.getValue();
        return (Long) this.sendRequest("GetRate", callValue).get("left");
    }
}
